package com.example.shellcollector.util;

import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager;
import android.content.pm.PackageManager;
import android.text.TextUtils;

import com.example.shellcollector.Entity.AppDailyUsage;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;

public class AppUsageCollectHelper {

    public static final int MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
    public static final int USAGE_EVENT_TRACK_DAYS_ASSUME = 9;

    /**
     * Get usage events for a specific app during a specific time range.
     * @param usageStatsManager UsageStatsManager object from Android
     * @param packageName package name of the app
     * @param startTime query start timestamp
     * @param endTime query end timestamp
     * @return a list of usage events of the specific app
     */
    public static List<UsageEvents.Event> getAppUsageEvents(
            UsageStatsManager usageStatsManager,
            String packageName,
            long startTime, long endTime) {

        List<UsageEvents.Event> events = new ArrayList<>();

        UsageEvents usageEvents = usageStatsManager.queryEvents(startTime, endTime);
        UsageEvents.Event currentEvent;
        while (usageEvents.hasNextEvent()) {
            currentEvent = new UsageEvents.Event();
            usageEvents.getNextEvent(currentEvent);

            if (currentEvent.getPackageName().equals(packageName) &&
                (currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED
                || currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED)) {
                events.add(currentEvent);
            }
        }

        return events;
    }

    public static long getTotalRunningTime(
            UsageStatsManager usageStatsManager, String packageName,
            long startTime, long endTime) {
        List<UsageEvents.Event> events =
                getAppUsageEvents(usageStatsManager, packageName, startTime, endTime);
        return getTotalRunningTime(startTime, endTime, events);
    }

    /**
     * Calculate app total running time during a specific time range.
     * @param startTime begin timestamp of the duration
     * @param endTime end timestamp of the duration
     * @return total running time in milliseconds
     */
    public static long getTotalRunningTime(
            long startTime, long endTime, List<UsageEvents.Event> events) {
        final int eventCount = events.size();
        long totalRunningTime = 0;

        if (eventCount > 1) {
            for (int i = 0; i < eventCount - 1; i++) {
                UsageEvents.Event e0 = events.get(i);
                UsageEvents.Event e1 = events.get(i + 1);
                if (e0.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED
                    && e1.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED) {
                    long diff = e1.getTimeStamp() - e0.getTimeStamp();
                    totalRunningTime += diff;
                }
            }
        }

        if (eventCount > 0) {
            UsageEvents.Event firstEvent = events.get(0);
            if (firstEvent.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED) {
                long diff = firstEvent.getTimeStamp() - startTime;
                totalRunningTime += diff;
            }

            UsageEvents.Event lastEvent = events.get(eventCount - 1);
            if (lastEvent.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
                long diff = endTime - lastEvent.getTimeStamp();
                totalRunningTime += diff;
            }
        }


        return totalRunningTime;
    }

    /**
     * Calculate app launch count.
     * @param events Event objects to calculate from
     * @return launch count
     */
    public static int getLaunchCount(List<UsageEvents.Event> events) {
        final int totalEventCount = events.size();
        int launchCount = 0;
        for (int i = 0; i < totalEventCount; i++) {
            UsageEvents.Event e = events.get(i);
            if (e.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
                launchCount++;
            }
        }

        return launchCount;
    }

    /**
     * Calculate app first running time.
     * @param startTime start timestamp of the time range given by user
     * @param events Event objects to calculate from
     * @return first running timestamp
     */
    public static long getFirstRunningTime(long startTime, List<UsageEvents.Event> events) {
        long firstRunningTime = startTime; // in case only got a PAUSED event

        boolean isFirstRunningTimeSet = false;
        for (int i = 0; i < events.size(); i++) {
            UsageEvents.Event e = events.get(i);
            if (e.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
                if (!isFirstRunningTimeSet) {
                    firstRunningTime = e.getTimeStamp();
                    isFirstRunningTimeSet = true;
                }
            }
        }

        return firstRunningTime;
    }

    /**
     * Calculate app last running time.
     * @param endTime end timestamp of the time range given by user
     * @param events Event objects to calculate from
     * @return last running timestamp
     */
    public static long getLastRunningTime(long endTime, List<UsageEvents.Event> events) {
        long lastRunningTime = endTime; // in case only got a RESUMED event

        boolean isLastRunningTimeSet = false;
        for (int i = events.size() - 1; i >= 0; i--) {
            UsageEvents.Event e = events.get(i);
            if (e.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED) {
                if (!isLastRunningTimeSet) {
                    lastRunningTime = e.getTimeStamp();
                    isLastRunningTimeSet = true;
                }
            }
        }

        return lastRunningTime;
    }

    /**
     * Group events by app name (package name).
     * @param usageStatsManager UsageStatsManager object from Android
     * @param startTime query range start timestamp
     * @param appDailyUsages empty map from app name to corresponding app usage entity,
     *                  gets populated after execution
     * @param sameEvents empty map from app name to corresponding app usage event list,
     *                   gets populated after execution
     */
    public static void groupAppDailyUsageEvents(
            UsageStatsManager usageStatsManager,
            long startTime,
            Map<String, AppDailyUsage> appDailyUsages,
            Map<String, List<UsageEvents.Event>> sameEvents) {

        long endTime = startTime + MILLISECONDS_PER_DAY;

        UsageEvents usageEvents = usageStatsManager.queryEvents(startTime, endTime);
        UsageEvents.Event currentEvent;
        while (usageEvents.hasNextEvent()) {
            currentEvent = new UsageEvents.Event();
            usageEvents.getNextEvent(currentEvent);

            if (currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED
                    || currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
                String key = currentEvent.getPackageName();
                if (appDailyUsages.get(key) == null) {
                    appDailyUsages.put(key, new AppDailyUsage());
                    sameEvents.put(key, new ArrayList<>());
                }
                Objects.requireNonNull(sameEvents.get(key)).add(currentEvent);
            }
        }
    }

//    /**
//     * Group events by app name (package name).
//     * @param usageStatsManager UsageStatsManager object from Android
//     * @param startTime query range start timestamp
//     * @param endTime query range end timestamp
//     * @param appUsages empty map from app name to corresponding app usage entity,
//     *                  gets populated after execution
//     * @param sameEvents empty map from app name to corresponding app usage event list,
//     *                   gets populated after execution
//     */
//    public static void groupAppUsageEvents(
//            UsageStatsManager usageStatsManager,
//            long startTime, long endTime,
//            Map<String, AppUsageEntity> appUsages,
//            Map<String, List<UsageEvents.Event>> sameEvents) {
//
//        UsageEvents usageEvents = usageStatsManager.queryEvents(startTime, endTime);
//        UsageEvents.Event currentEvent;
//        while (usageEvents.hasNextEvent()) {
//            currentEvent = new UsageEvents.Event();
//            usageEvents.getNextEvent(currentEvent);
//
//            if (currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED
//                    || currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
//                String key = currentEvent.getPackageName();
//                if (appUsages.get(key) == null) {
//                    appUsages.put(key, new AppUsageEntity());
//                    sameEvents.put(key, new ArrayList<>());
//                }
//                Objects.requireNonNull(sameEvents.get(key)).add(currentEvent);
//            }
//        }
//    }

    /**
     * Retrieve app usage information and set them to app usage entities.
     * @param startTime query range start timestamp
     * @param appDailyUsages map from app name to corresponding app daily usage entity,
     *                  gets updated after execution
     * @param sameEvents map from app name to corresponding app usage event list
     */
    public static void collectAppDailyUsageFromMaps(
            long startTime,
            Map<String, AppDailyUsage> appDailyUsages,
            Map<String, List<UsageEvents.Event>> sameEvents) {
        for (Map.Entry<String, List<UsageEvents.Event>> entry : sameEvents.entrySet()) {
            List<UsageEvents.Event> events = entry.getValue();
            String packageName = entry.getKey();

            AppDailyUsage appDailyUsage = appDailyUsages.get(packageName);
            long endTime = startTime + MILLISECONDS_PER_DAY;

            int launchCount = getLaunchCount(events);
            long firstRunningTime = getFirstRunningTime(startTime, events);
            long lastRunningTime = getLastRunningTime(endTime, events);
            long totalRunningTime = getTotalRunningTime(startTime, endTime, events);

            // set AppDailyUsage fields
            assert appDailyUsage != null;
            // remember to set appId later
            appDailyUsage.setLaunchCount(launchCount);
            appDailyUsage.setDate(startTime);
            appDailyUsage.setFirstRunningTime(firstRunningTime);
            appDailyUsage.setLastRunningTime(lastRunningTime);
            appDailyUsage.setTotalRunningTime(totalRunningTime);
        }
    }

//    /**
//     * Retrieve app usage information and set them to app usage entities.
//     * @param startTime query range start timestamp
//     * @param endTime query range end timestamp
//     * @param appUsages map from app name to corresponding app usage entity,
//     *                  gets updated after execution
//     * @param sameEvents map from app name to corresponding app usage event list
//     */
//    public static void collectAppUsageFromMaps(
//            long startTime, long endTime,
//            Map<String, AppUsageEntity> appUsages,
//            Map<String, List<UsageEvents.Event>> sameEvents) {
//        for (Map.Entry<String, List<UsageEvents.Event>> entry : sameEvents.entrySet()) {
//            List<UsageEvents.Event> events = entry.getValue();
//            int totalEventCount = events.size();
//            AppUsageEntity appUsageEntity = appUsages.get(entry.getKey());
//
//            int launchCount;
//            long appFirstRunningTime;
//            long appLastRunningTime;
//            long appTotalRunningTime;
//
//            launchCount = getLaunchCount(events);
//
////            // calculate first running time
////            boolean isFirstRunningTimeSet = false;
////            for (int i = 0; i < totalEventCount; i++) {
////                UsageEvents.Event e = events.get(i);
////                if (e.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
////                    if (!isFirstRunningTimeSet) {
////                        appFirstRunningTime = e.getTimeStamp();
////                        isFirstRunningTimeSet = true;
////                    }
////                }
////            }
//
//            appFirstRunningTime = getFirstRunningTime(startTime, events);
//
////            // calculate last running time
////            boolean isLastRunningTimeSet = false;
////            for (int i = totalEventCount - 1; i >= 0; i--) {
////                UsageEvents.Event e = events.get(i);
////                if (e.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED) {
////                    if (!isLastRunningTimeSet) {
////                        appLastRunningTime = e.getTimeStamp();
////                        isLastRunningTimeSet = true;
////                    }
////                }
////            }
//
//            appLastRunningTime = getLastRunningTime(endTime, events);
//
//
//            // calculate total running time
//            appTotalRunningTime = getTotalRunningTime(startTime, endTime, events);
//
//
//            // set AppUsageEntity fields
//            assert appUsageEntity != null;
//            appUsageEntity.setAppPackageName(entry.getKey());
//            appUsageEntity.setLaunchCount(launchCount);
//            appUsageEntity.setAppFirstRunningTime(appFirstRunningTime);
//            appUsageEntity.setAppLastRunningTime(appLastRunningTime);
//            appUsageEntity.setAppTotalRunningTime(appTotalRunningTime);
//        }
//    }

    /**
     * Query the label of an app from the installed apps with the given package name.
     * @param pm PackageManager object from Android
     * @param packageName the package name of an app
     * @return app label registered in <application/> markup
     */
    public static String getAppLabelFromPackageName(PackageManager pm, String packageName) {

        String appLabel;
        try {
            appLabel = pm.getApplicationInfo(packageName, 0).loadLabel(pm).toString();
        } catch (PackageManager.NameNotFoundException e) {
            // use package name for apps without a label
            appLabel = packageName;
        }

        return appLabel;
    }

//    /**
//     * Use app labels to represent an app instead of the package name.
//     * @param pm PackageManager object from Android
//     * @param appUsageEntityList app usage entities whose names need to be updated
//     */
//    public static void loadAppLabels(PackageManager pm, List<AppUsageEntity> appUsageEntityList) {
//        for (AppUsageEntity appUsage : appUsageEntityList) {
//            String appLabel = getAppLabelFromPackageName(pm, appUsage.getAppPackageName());
//            if (!TextUtils.isEmpty(appLabel)) {
//                appUsage.setAppName(appLabel);
//            }
//        }
//    }

    /**
     * Get a timestamp representing some days ago.
     * @param daysAgo how many days back
     * @return timestamp in milliseconds
     */
    public static long getPreviousDaysTimestamp(int daysAgo) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_YEAR, -daysAgo);
        return calendar.getTimeInMillis();
    }

    /**
     * Cut the time stamp trailing milliseconds.
     * @param timestamp a calendar object representing a date (but has some trailing milliseconds)
     * @return a timestamp represent start time of the day (00:00 in 24-hour-form)
     */
    public static long getStartTimeOfDay(long timestamp) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(timestamp);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTimeInMillis();
    }

    /**
     * Cut the time stamp trailing milliseconds according to GMT+8.
     * @param timestamp a calendar object representing a date (but has some trailing milliseconds)
     * @return a timestamp represent start time of the day (00:00 in 24-hour-form)
     */
    public static long getStartTimeOfDayLocal(long timestamp) {
        timestamp = getStartTimeOfDay(timestamp);
//        long offset = TimeZone.getTimeZone("GMT+8").getRawOffset();
//        timestamp -= offset;
        return timestamp;
    }
}
